import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, appendFileSync, statSync, renameSync, unlinkSync } from 'node:fs'
import { join, dirname, resolve, relative } from 'node:path'
import { PluginRuntimeManager } from '@bai-hu/plugin-host-core'
import { fileURLToPath } from 'node:url'
import chokidar from 'chokidar'
import Ajv from 'ajv'
import { JsonStore } from './configStore'
import { requireAuth, rateLimit, csrfProtection } from './middleware/auth'
import { getConfig } from './config'
import { metricsCollector, metricsMiddleware } from './monitoring/metrics'
import { logger, LogLevel } from './logging/logger'
import { healthChecker } from './health/healthCheck'

// Load configuration
const config = getConfig()

// Initialize logger with config
logger.setLevel(config.logLevel === 'debug' ? LogLevel.DEBUG :
                config.logLevel === 'info' ? LogLevel.INFO :
                config.logLevel === 'warn' ? LogLevel.WARN : LogLevel.ERROR)

const app = new Hono()

// Enhanced metrics middleware
app.use('*', metricsMiddleware())

// Request monitoring middleware with enhanced logging
app.use('*', async (c, next) => {
  systemStats.requests.total++
  const start = Date.now()

  try {
    await next()
    const duration = Date.now() - start
    if (duration > 1000) { // Log slow requests
      logger.warn(`Slow request: ${c.req.method} ${c.req.path} took ${duration}ms`, {
        method: c.req.method,
        path: c.req.path,
        duration,
        status: c.res.status
      })
    }
  } catch (error: any) {
    systemStats.requests.errors++
    systemStats.requests.lastError = error.message
    logger.error(`Request error: ${c.req.method} ${c.req.path}`, error, {
      method: c.req.method,
      path: c.req.path,
      duration: Date.now() - start
    })
    throw error
  }
})

// Apply global rate limiting using config
app.use('*', rateLimit(config.rateLimitRequests, config.rateLimitWindow))

// Apply CSRF protection to state-changing operations
app.use('*', csrfProtection())

// Security utilities
function securePath(basePath: string, userPath: string): string | null {
  try {
    const resolvedBase = resolve(basePath)
    const resolvedUser = resolve(basePath, userPath)

    // Check if the resolved path is within the base directory
    if (!resolvedUser.startsWith(resolvedBase + '/') && resolvedUser !== resolvedBase) {
      return null // Path traversal attempt
    }

    return resolvedUser
  } catch {
    return null
  }
}

function getMimeType(filePath: string): string {
  if (filePath.endsWith('.js') || filePath.endsWith('.mjs')) {
    return 'application/javascript; charset=utf-8'
  }
  if (filePath.endsWith('.css')) {
    return 'text/css; charset=utf-8'
  }
  if (filePath.endsWith('.json') || filePath.endsWith('.map')) {
    return 'application/json; charset=utf-8'
  }
  if (filePath.endsWith('.html')) {
    return 'text/html; charset=utf-8'
  }
  if (filePath.endsWith('.png')) {
    return 'image/png'
  }
  if (filePath.endsWith('.jpg') || filePath.endsWith('.jpeg')) {
    return 'image/jpeg'
  }
  if (filePath.endsWith('.svg')) {
    return 'image/svg+xml'
  }
  return 'application/octet-stream'
}

// Enhanced health check endpoint with detailed checks
app.get('/api/health', async (c) => {
  const uptime = process.uptime()
  const memory = process.memoryUsage()
  const enabledPlugins = Object.values(registry).filter(p => p.enabled)
  const runningPlugins = Array.from(handles.keys())

  // Update plugin metrics for the metrics collector
  metricsCollector.recordPluginMetrics(
    Object.keys(registry).length,
    enabledPlugins.length,
    systemStats.plugins.failures
  )

  // Run detailed health checks
  const healthStatus = await healthChecker.runChecks()

  const health = {
    status: healthStatus.status,
    timestamp: new Date().toISOString(),
    uptime: Math.floor(uptime),
    version: process.env.npm_package_version || '0.0.0',
    system: {
      nodeVersion: process.version,
      platform: process.platform,
      arch: process.arch,
      memory: {
        used: Math.round(memory.heapUsed / 1024 / 1024), // MB
        total: Math.round(memory.heapTotal / 1024 / 1024), // MB
        external: Math.round(memory.external / 1024 / 1024), // MB
        rss: Math.round(memory.rss / 1024 / 1024) // MB
      }
    },
    requests: {
      total: systemStats.requests.total,
      errors: systemStats.requests.errors,
      errorRate: systemStats.requests.total > 0 ?
        (systemStats.requests.errors / systemStats.requests.total * 100).toFixed(2) + '%' : '0%',
      lastError: systemStats.requests.lastError
    },
    plugins: {
      total: Object.keys(registry).length,
      enabled: enabledPlugins.length,
      running: runningPlugins.length,
      watchers: watchers.size,
      activations: systemStats.plugins.activations,
      failures: systemStats.plugins.failures,
      lastFailure: systemStats.plugins.lastFailure,
      list: enabledPlugins.map(p => ({
        name: p.name,
        enabled: p.enabled,
        running: runningPlugins.includes(p.name.split('/').pop() || ''),
        info: handles.get(p.name.split('/').pop() || '')?.info()
      }))
    },
    checks: healthStatus.checks
  }

  return c.json(health)
})

// Simple health check for load balancers
app.get('/api/health/simple', (c) => c.text('ok'))

// Enhanced Prometheus metrics endpoint
app.get('/api/metrics', (c) => {
  const enabledPlugins = Object.values(registry).filter(p => p.enabled)
  const runningPlugins = Array.from(handles.keys())

  // Update plugin metrics for the metrics collector
  metricsCollector.recordPluginMetrics(
    Object.keys(registry).length,
    enabledPlugins.length,
    systemStats.plugins.failures
  )

  // Get enhanced metrics from the collector
  const enhancedMetrics = metricsCollector.getPrometheusMetrics()

  // Add additional system-specific metrics
  const additionalMetrics = [
    '',
    '# HELP baihu_plugins_running Number of running plugins',
    '# TYPE baihu_plugins_running gauge',
    `baihu_plugins_running ${runningPlugins.length}`,
    '',
    '# HELP baihu_plugins_activations_total Total plugin activations',
    '# TYPE baihu_plugins_activations_total counter',
    `baihu_plugins_activations_total ${systemStats.plugins.activations}`,
    '',
    '# HELP baihu_plugins_failures_total Total plugin failures',
    '# TYPE baihu_plugins_failures_total counter',
    `baihu_plugins_failures_total ${systemStats.plugins.failures}`,
    '',
    '# HELP baihu_watchers_active Number of active file watchers',
    '# TYPE baihu_watchers_active gauge',
    `baihu_watchers_active ${watchers.size}`,
    ''
  ].join('\n')

  const allMetrics = enhancedMetrics + additionalMetrics

  return c.text(allMetrics, 200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' })
})

// Resolve monorepo root based on this file's location
const monorepoRoot = fileURLToPath(new URL('../../..', import.meta.url))
// Dynamic plugins dir from config
const pluginsDir = resolve(config.pluginsDir)

function getPaths(id: string) {
  const root = join(pluginsDir, id)
  return {
    root,
    manifestPath: join(root, 'src/plugin.json'),
    pluginDist: join(root, 'dist'),
    pluginFsWebEntry: join(root, 'dist/web/entry.js'),
  }
}

// Simple persisted registry using config
const dataDir = resolve(config.dataDir)
const dataFile = join(dataDir, 'plugins.json')
let registry: Record<string, { name: string; manifest: string; enabled: boolean }> = {}

function discoverPlugins() {
  try {
    mkdirSync(dirname(dataFile), { recursive: true })
    const entries = readdirSync(pluginsDir, { withFileTypes: true })
      .filter((d) => d.isDirectory())
      .map((d) => d.name)
      .filter((id) => existsSync(getPaths(id).manifestPath))
    for (const id of entries) {
      const name = `@bai-hu-plugin/${id}`
      const manifest = `/api/plugins/${id}/manifest`
      if (!registry[id]) registry[id] = { name, manifest, enabled: false }
      else registry[id].manifest = manifest // Update existing entries
    }
  } catch (e) {
    console.warn('Failed to discover plugins', e)
  }
}

function loadRegistry() {
  try {
    if (existsSync(dataFile)) {
      const disk = JSON.parse(readFileSync(dataFile, 'utf-8'))
      registry = { ...registry, ...disk }
    }
  } catch (e) {
    console.warn('Failed to load registry, using defaults', e)
  }
  discoverPlugins()
  saveRegistry()
}
function saveRegistry() {
  try {
    writeFileSync(dataFile, JSON.stringify(registry, null, 2))
  } catch (e) {
    console.warn('Failed to save registry', e)
  }
}
loadRegistry()

// Config store (per plugin) using config
const auditFile = join(dataDir, 'audit.log')
const configStore = new JsonStore<Record<string, any>>(join(dataDir, 'plugin-config.json'), {})
const ajv = new Ajv({ useDefaults: true })

app.get('/api/plugins', (c) => c.json(Object.values(registry)))

// Generic plugin manifest route
app.get('/api/plugins/:id/manifest', (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()
  const { manifestPath, pluginFsWebEntry } = getPaths(id)
  const raw = readFileSync(manifestPath, 'utf-8')
  const manifest = JSON.parse(raw)
  manifest.frontend.devFsEntry = `/@fs/${pluginFsWebEntry}`
  manifest.frontend.entry = `/api/plugins/${id}/web/entry.js`
  const all = configStore.load()
  manifest.config = all[id] ?? {}
  if (manifest.backend?.entry) manifest.backend.entry = `/api/plugins/${id}/server/entry.js`
  return c.json(manifest)
})

// Generic static files for plugin (web/server bundles) - keep this after specific routes above
app.get('/api/plugins/:id/*', (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()

  const { pluginDist } = getPaths(id)
  const reqPath = c.req.path.replace(new RegExp(`^/api/plugins/${id}/?`), '')

  // Security: Prevent path traversal attacks
  const secureFilePath = securePath(pluginDist, reqPath)
  if (!secureFilePath) {
    console.warn(`[security] Path traversal attempt blocked: ${reqPath} for plugin ${id}`)
    return c.notFound()
  }

  if (!existsSync(secureFilePath)) return c.notFound()

  try {
    const body = readFileSync(secureFilePath)
    const contentType = getMimeType(secureFilePath)
    return new Response(body, { headers: { 'content-type': contentType } })
  } catch (error) {
    console.error(`[security] File read error for plugin ${id}:`, error)
    return c.text('Internal Server Error', 500)
  }
})

// --- backend plugin runtime wiring ---
const runtime = new PluginRuntimeManager()
const handles = new Map<string, Awaited<ReturnType<typeof runtime.start>>>()
const watchers = new Map<string, ReturnType<typeof chokidar.watch>>()

// System monitoring
const systemStats = {
  startTime: Date.now(),
  requests: {
    total: 0,
    errors: 0,
    lastError: null as string | null
  },
  plugins: {
    activations: 0,
    failures: 0,
    lastFailure: null as string | null
  }
}

function rotateAuditIfNeeded() {
  try {
    if (!existsSync(auditFile)) return
    const { size } = statSync(auditFile)
    const limit = config.auditMaxBytes
    if (size < limit) return
    const ts = new Date().toISOString().replace(/[:.]/g, '-')
    const rotated = auditFile.replace(/\.log$/, `.${ts}.log`)
    renameSync(auditFile, rotated)
    // cleanup older rotated files (keep last 5)
    const dir = dirname(auditFile)
    const base = auditFile.split('/').pop()!.replace(/\.log$/, '')
    const files = readdirSync(dir)
      .filter((f) => f.startsWith(base + '.') && f.endsWith('.log'))
      .sort()
    while (files.length > 5) {
      const f = files.shift()!
      try { unlinkSync(join(dir, f)) } catch {}
    }
  } catch {}
}

function auditToConsole(rec: any) {
  const { ts, plugin, method, allowed, resource, error } = rec
  const line = JSON.stringify({ ts, plugin, method, allowed, resource, error }) + '\n'
  try {
    mkdirSync(dataDir, { recursive: true })
    rotateAuditIfNeeded()
    appendFileSync(auditFile, line)
  } catch (e) {
    if (allowed) console.log(`[audit] ${ts} plugin=${plugin} method=${method} allowed resource=${JSON.stringify(resource || {})}`)
    else console.warn(`[audit] ${ts} plugin=${plugin} method=${method} DENY error=${error?.message} resource=${JSON.stringify(resource || {})}`)
  }
}

async function startBackend(id: string) {
  try {
    const { manifestPath, pluginDist } = getPaths(id)
    const raw = readFileSync(manifestPath, 'utf-8')
    const manifest = JSON.parse(raw)
    const entry = join(pluginDist, 'server/entry.js')
    const allCfg = configStore.load()
    const cfg = allCfg[id] ?? {}

    const handle = await runtime.start({
      name: manifest.name,
      version: manifest.version,
      entry,
      kind: 'thread',
      manifest: { ...manifest, config: cfg },
      audit: auditToConsole,
      autoRestart: true,
      maxRestarts: 3,
      restartDelay: 2000
    })

    // Setup exit monitoring
    handle.onExit?.((code, signal) => {
      logger.warn(`Plugin worker exited: ${id}`, {
        pluginId: id,
        exitCode: code,
        signal,
        timestamp: new Date().toISOString()
      })
      if (code !== 0 && code !== null) {
        systemStats.plugins.failures++
        systemStats.plugins.lastFailure = `${id}: Worker crashed (code: ${code})`
      }
    })

    handles.set(id, handle)
    systemStats.plugins.activations++
    logger.info(`Plugin backend started successfully: ${id}`, {
      pluginId: id,
      pluginName: manifest.name,
      version: manifest.version
    })
  } catch (error: any) {
    systemStats.plugins.failures++
    systemStats.plugins.lastFailure = `${id}: ${error.message}`
    logger.error(`Plugin backend start failed: ${id}`, error, {
      pluginId: id
    })
    throw error
  }
}
async function stopBackend(id: string) {
  const h = handles.get(id)
  try {
    await h?.stop()
  } finally {
    handles.delete(id)
  }
}
async function hotReload(id: string) {
  const { manifestPath, pluginDist } = getPaths(id)
  const raw = readFileSync(manifestPath, 'utf-8')
  const manifest = JSON.parse(raw)
  const entry = join(pluginDist, 'server/entry.js')
  const allCfg = configStore.load()
  const cfg = allCfg[id] ?? {}
  const prev = handles.get(id)
  if (!prev) return startBackend(id)
  const next = await runtime.hotReload(prev, { name: manifest.name, version: manifest.version, entry, kind: 'thread', manifest: { ...manifest, config: cfg }, audit: auditToConsole })
  handles.set(id, next)
}
function ensureWatcher(id: string) {
  if (watchers.has(id)) return
  const { pluginDist } = getPaths(id)
  const watcher = chokidar.watch(join(pluginDist, 'server/entry.js'), { ignoreInitial: true })
  watcher.on('change', async () => {
    if (!registry[id]?.enabled) return
    try {
      await hotReload(id)
      console.log(`[plugin:${id}] backend reloaded`)
    } catch (e) {
      console.error(`[plugin:${id}] backend reload failed`, e)
    }
  })
  watchers.set(id, watcher)
  console.debug(`[plugin:${id}] file watcher started`)
}

function stopWatcher(id: string) {
  const watcher = watchers.get(id)
  if (watcher) {
    watcher.close()
    watchers.delete(id)
    console.debug(`[plugin:${id}] file watcher stopped`)
  }
}

// Plugin management endpoints - require authentication
app.post('/api/plugins/:id/enable', requireAuth(), async (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()
  registry[id].enabled = true
  saveRegistry()
  if (!handles.get(id)) await startBackend(id)
  ensureWatcher(id)
  console.info(`[admin] Plugin ${id} enabled`)
  return c.json({ ok: true })
})

app.post('/api/plugins/:id/disable', requireAuth(), async (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()
  registry[id].enabled = false
  saveRegistry()
  await stopBackend(id)
  stopWatcher(id) // Fix: Stop file watcher to prevent resource leak
  console.info(`[admin] Plugin ${id} disabled`)
  return c.json({ ok: true })
})

// Config endpoints
app.get('/api/plugins/:id/config', (c) => {
  const id = c.req.param('id')
  const all = configStore.load()
  return c.json(all[id] ?? {})
})

app.post('/api/plugins/:id/config', requireAuth(), async (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()
  const { manifestPath } = getPaths(id)
  const raw = readFileSync(manifestPath, 'utf-8')
  const manifest = JSON.parse(raw)
  const schema = manifest.configSchema || { type: 'object' }
  const validate = ajv.compile(schema)
  const body = await c.req.json()
  const data = { ...body }
  if (!validate(data)) {
    return c.json({ ok: false, errors: validate.errors }, 400)
  }
  const all = configStore.load()
  all[id] = data
  configStore.save(all)
  if (handles.get(id)) await hotReload(id)
  console.info(`[admin] Plugin ${id} config updated`)
  return c.json({ ok: true })
})

// Generic manual reload
app.post('/api/plugins/:id/reload', requireAuth(), async (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()
  await hotReload(id)
  const info = handles.get(id)?.info()
  console.info(`[admin] Plugin ${id} manually reloaded`)
  return c.json({ ok: true, info })
})

// Manual restart (different from reload - creates new worker)
app.post('/api/plugins/:id/restart', requireAuth(), async (c) => {
  const id = c.req.param('id')
  if (!registry[id]) return c.notFound()

  const handle = handles.get(id)
  if (!handle) {
    return c.json({ ok: false, error: 'Plugin not running' }, 400)
  }

  try {
    await handle.restart?.()
    const info = handle.info()
    console.info(`[admin] Plugin ${id} manually restarted`)
    return c.json({ ok: true, info })
  } catch (error: any) {
    console.error(`[admin] Plugin ${id} restart failed:`, error.message)
    return c.json({ ok: false, error: error.message }, 500)
  }
})

// Admin: recent audit entries - require authentication
app.get('/api/admin/audit', requireAuth(), (c) => {
  const limit = Math.max(1, Math.min(1000, Number(c.req.query('limit') || '100')))
  function readRecentAudit(n: number) {
    try { mkdirSync(dataDir, { recursive: true }) } catch {}
    const dir = dataDir
    const base = 'audit'
    const files = readdirSync(dir)
      .filter((f) => f.startsWith(base) && f.endsWith('.log'))
      .sort()
      .reverse()
    const idx = files.indexOf('audit.log')
    if (idx > 0) { files.splice(idx, 1); files.unshift('audit.log') }
    const items: any[] = []
    for (const f of files) {
      try {
        const content = readFileSync(join(dir, f), 'utf-8')
        const lines = content.split('\n')
        for (let i = lines.length - 1; i >= 0 && items.length < n; i--) {
          const line = lines[i].trim()
          if (!line) continue
          try { items.push(JSON.parse(line)) } catch {}
        }
        if (items.length >= n) break
      } catch {}
    }
    return items
  }
  const items = readRecentAudit(limit)
  return c.json({ items, count: items.length })
})

const port = config.port

// Admin: rescan plugins directory and refresh registry
app.post('/api/admin/plugins/rescan', requireAuth(), (c) => {
  discoverPlugins()
  saveRegistry()
  console.info('[admin] Plugin directory rescanned')
  return c.json({ ok: true, plugins: Object.values(registry) })
})

// Graceful shutdown handler
async function gracefulShutdown() {
  console.log('\n[shutdown] Graceful shutdown initiated...')

  // Stop all plugin backends
  const stopPromises = Array.from(handles.keys()).map(async (id) => {
    try {
      await stopBackend(id)
      console.log(`[shutdown] Stopped plugin backend: ${id}`)
    } catch (e) {
      console.error(`[shutdown] Error stopping plugin ${id}:`, e)
    }
  })

  // Close all file watchers
  for (const [id, watcher] of watchers.entries()) {
    try {
      watcher.close()
      console.log(`[shutdown] Closed watcher for plugin: ${id}`)
    } catch (e) {
      console.error(`[shutdown] Error closing watcher for ${id}:`, e)
    }
  }
  watchers.clear()

  await Promise.all(stopPromises)
  console.log('[shutdown] All plugins stopped, exiting...')
  process.exit(0)
}

// Register shutdown handlers
process.on('SIGTERM', gracefulShutdown)
process.on('SIGINT', gracefulShutdown)

serve({ fetch: app.fetch, port, hostname: config.host })

// Start enabled plugins and watchers at boot
for (const id of Object.keys(registry)) {
  if (registry[id]?.enabled) {
    startBackend(id).catch((e) => console.error(`[plugin:${id}] backend start failed`, e))
    ensureWatcher(id)
  }
}

console.log(`@bai-hu/server listening on http://${config.host}:${port}`)
console.log(`[config] Environment: ${config.nodeEnv}`)
console.log(`[config] Plugins directory: ${config.pluginsDir}`)
console.log(`[config] Data directory: ${config.dataDir}`)
console.log(`[security] Admin API key: ${config.adminApiKey === 'dev-admin-key-change-in-production' ? 'using default (CHANGE IN PRODUCTION!)' : 'configured'}`)

export default app

